package com.sunday.common.http.apache.sentinel;


import com.alibaba.csp.sentinel.*;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.util.StringUtil;
import com.sunday.common.core.enums.HttpExpandStatusEnum;
import com.sunday.common.http.apache.async.DefaultAsyncExecCallback;
import com.sunday.common.http.apache.sentinel.config.SentinelApacheHttpClientConfig;
import lombok.extern.slf4j.Slf4j;
import org.apache.hc.client5.http.async.AsyncExecChainHandler;
import org.apache.hc.client5.http.impl.async.HttpAsyncClientBuilder;
import org.apache.hc.core5.concurrent.FutureCallback;
import org.apache.hc.core5.http.EntityDetails;
import org.apache.hc.core5.http.HttpHost;
import org.apache.hc.core5.http.HttpResponse;
import org.apache.hc.core5.http.config.NamedElementChain;
import org.apache.hc.core5.http.message.BasicHttpResponse;
import org.apache.hc.core5.http.nio.AsyncRequestProducer;
import org.apache.hc.core5.http.nio.AsyncResponseConsumer;
import org.apache.hc.core5.http.nio.HandlerFactory;
import org.apache.hc.core5.http.protocol.HttpContext;

/**
 * SentinelApacheHttpAsyncClientBuilder类继承了HttpAsyncClientBuilder，它用于创建一个与阿里巴巴的Sentinel库整合，用于异步限流和熔断器的实现的Apache Http异步客户端。
 * 和之前的同步实现类似，这个类有一个成员变量config，也就是 SentinelApacheHttpClientConfig 的实例，用于存储配置信息。构造函数也一样，用于创建类的新实例。
 * 不过，在customizeExecChain方法中，处理器的实现有些不同。这个处理器不仅接收HTTP请求和执行链，还接收一个AsyncExecCallback类型的参数，这个参数是异步回调接口，用于处理异步请求的执行结果。
 * entry 是 SphU.entry 创建的，表示对资源的访问尝试。name是通过调用config对象的前缀和extractor方法，结合请求获取的。如果获取资源定位器的入口被Sentinel拦截（例如，流量超过阈值），它将抛出一个 BlockException ，然后会调用停止回调中的failed 方法。如果在请求执行过程中发生其他错误，那么异常将被追踪，然后抛出。
 * 不论请求是否成功，释放资源定位前首先要检查 entry 是否为 null ，如果不为 null ，则调用它的 exit 方法，表示资源使用结束。在这种情况下，将存在一对 entry 和 exit。
 * 所以，这个类的功能就是创建一个定制的Apache Http异步客户端，与Sentinel进行整合，能够处理Sentinel的限流和熔断，并提供异步请求处理的能力，可以更好地适应高并发、高延迟的场景。
 *
 * @author zsy
 * @see com.alibaba.csp.sentinel.adapter.apache.httpclient.SentinelApacheHttpClientBuilder
 * @since 2024/1/19
 */
@Slf4j
public class SentinelApacheHttpAsyncClientBuilder extends HttpAsyncClientBuilder {

    private static final String SENTINEL_EXEC_CHAIN_HANDLER_NAME = "ASYNC_SENTINEL";

    private final SentinelApacheHttpClientConfig config;

    public SentinelApacheHttpAsyncClientBuilder() {
        this(new SentinelApacheHttpClientConfig());
    }

    public SentinelApacheHttpAsyncClientBuilder(SentinelApacheHttpClientConfig config) {
        assert config != null : "config cannot be null";
        this.config = config;
    }

    /**
     * ==================  AsyncExecCallback =================
     * <p>
     * 在 Apache HttpAsyncClient 中，AsyncExecCallback 接口定义了一些方法用于处理异步请求的执行结果。下面是这些方法的触发时机：
     * handleResponse: 当接收到异步响应时触发该方法。可以在这个方法中对响应进行处理，如解析响应内容、处理状态码等。
     * handleInformationResponse: 当接收到异步信息响应（1xx）时触发该方法。信息响应是指状态码为 1xx 的响应，如 100 Continue。
     * completed: 当异步请求成功完成并且响应已经处理完成时触发该方法。可以在这个方法中进行成功响应的后续处理，如解析响应内容、提取数据等。
     * failed: 当异步请求执行出错时触发该方法。例如，当网络连接失败、请求超时或服务器返回错误状态码等情况。
     * 这些方法的具体触发时机和调用方式可能依赖于异步请求的执行链的实现和配置。需要根据具体的使用场景和代码逻辑来确定这些方法的触发时机。
     * 通常情况下，handleResponse 和 handleInformationResponse 方法在接收到响应时被调用，而 completed 方法在异步请求成功完成且响应处理完毕时被调用。而 failed 方法则在请求执行过程中出现错误时被调用。
     * 请注意，以上仅为一般情况，具体的触发时机可能因请求执行链的配置、服务器响应和网络状况等因素而有所变化。
     * <p>
     * ==================  handleResponse | handleInformationResponse =================
     * 在 Apache HttpAsyncClient 中，handleResponse 和 handleInformationResponse 是用于处理异步响应的两个方法，它们之间有以下区别：
     * handleResponse 方法：
     * 触发时机：当接收到非信息性的异步响应（2xx、3xx、4xx、5xx）时触发。
     * 功能：该方法用于处理正常的响应，例如解析响应内容、处理状态码、提取数据等。它适用于对实际的响应进行操作和处理。
     * handleInformationResponse 方法：
     * 触发时机：当接收到信息性的异步响应（1xx）时触发。
     * 功能：该方法用于处理信息性的响应，例如 100 Continue 响应。信息响应表示服务器正在发送一系列的响应，但最终的响应尚未完全接收。通常情况下，这些响应不包含实际的响应内容，而是用于客户端和服务器之间的通信协议处理。
     * 总结来说，handleResponse 用于处理非信息性的异步响应，而 handleInformationResponse 则用于处理信息性的异步响应。它们的区别主要体现在触发时机和处理的响应类型上。根据具体的业务需求和响应类型，选择适合的方法来处理异步响应。
     */
    @Override
    protected void customizeExecChain(final NamedElementChain<AsyncExecChainHandler> execChainDefinition) {
        execChainDefinition.addFirst((request, entityProducer, scope, chain, asyncExecCallback) -> {
            Entry entry = null;
            try {

                String name = config.getExtractor().extractor(request);
                if (!StringUtil.isEmpty(config.getPrefix())) {
                    name = config.getPrefix() + name;
                }
                entry = SphU.entry(name, ResourceTypeConstants.COMMON_WEB, EntryType.OUT);
                chain.proceed(request, entityProducer, scope, new DefaultAsyncExecCallback(entry, asyncExecCallback));
            } catch (BlockException e) {
                /**
                 * BlockException 引发的时候不会产生 entry 对象, 因此省略 if (entry != null) entry.exit();
                 * 特别说明异步中不能使用finally 释放资源，因为异步执行的时候，资源已经释放了，所以这里不需要释放资源
                 * @see DefaultAsyncExecCallback
                 *
                 * 模拟返回一个熔断的响应
                 * @see org.apache.hc.client5.http.impl.async.InternalAbstractHttpAsyncClient#doExecute(HttpHost, AsyncRequestProducer, AsyncResponseConsumer, HandlerFactory, HttpContext, FutureCallback)
                 * @see org.apache.hc.client5.http.async.AsyncExecCallback#handleResponse(HttpResponse, EntityDetails)
                 */
                asyncExecCallback.handleResponse(new BasicHttpResponse(HttpExpandStatusEnum.CIRCUIT_BREAK.code), null);
            } catch (Exception ex) {
                if (entry != null) entry.exit();
                Tracer.traceEntry(ex, entry);
                asyncExecCallback.failed(ex);
            } catch (Throwable t) {
                if (entry != null) entry.exit();
                Tracer.traceEntry(t, entry);
                throw t;
            }
        }, SENTINEL_EXEC_CHAIN_HANDLER_NAME);
    }

}
